﻿// <copyright file="TTableStorageEngineTest.cs" company="N/A">
//     Copyright (c) Nicholas Barrett and Benjamin Kasten.  All rights reserved.
//
//     This program is free software; you can redistribute it and/or modify it
//     under the terms of the GNU General Public License as published by the
//     Free Software Foundation; either version 2 of the License, or (at your
//     option) any later version.
//
//     This program is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
//     Public License for more details.
//
//     You should have received a copy of the GNU General Public License along
//     with this program; if not, write to the Free Software Foundation, 
//     Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// </copyright>

namespace BetterAzureTableStorage.Tests
{
    using System;
    using System.Collections.Generic;
    using System.Data.Services.Client;
    using System.Linq;
    using System.Net;
    using System.Reflection;
    using System.Xml;
    using System.Xml.Linq;
    using BetterAzureTableStorage;
    using Fiddler;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;

    /// <summary>
    /// This is a test class for TTableStorageEngineTest and is intended
    /// to contain all TTableStorageEngineTest Unit Tests
    ///</summary>
    [TestClass()]
    public class TTableStorageEngineTest
    {
        [ClassInitialize]
        public static void TTableStorageEngine_ClassInit(TestContext context)
        {
            //FiddlerApplication.Startup(8087, FiddlerCoreStartupFlags.Default | FiddlerCoreStartupFlags.DecryptSSL);
            //FiddlerApplication.BeforeResponse += new SessionStateHandler(FiddlerApplication_BeforeResponse);
            Credentials = CloudStorageAccount.DevelopmentStorageAccount.Credentials;
            ////ServiceRoot = CloudStorageAccount.DevelopmentStorageAccount.TableEndpoint;
            ServiceRoot = new Uri("http://ipv4.fiddler:10002/devstoreaccount1");
        }

        [ClassCleanup]
        public static void TTableStorageEngine_ClassCleanup()
        {
            ////FiddlerApplication.Shutdown();
        }

        static void FiddlerApplication_BeforeResponse(Session oSession)
        {
        }

        /// <summary>
        /// Gets or sets the credentials.
        /// </summary>
        /// <value>The credentials.</value>
        static StorageCredentials Credentials { get; set; }

        /// <summary>
        /// Gets or sets the service root.
        /// </summary>
        /// <value>The service root.</value>
        static Uri ServiceRoot { get; set; }

        public TTableStorageEngine GetTestTableEntityDotEngine()
        {
            return new TTableStorageEngine(
                ServiceRoot,
                Credentials,
                new List<TTableStorageInfo>()
                {
                    new TTableStorageInfo()
                    {     
                     
                        ClassType = typeof(TTestTableEntityDot),
                        TableName = typeof(TTestTableEntityDot).Name,
                        PropertyList = new List<TTablePropertyInfo>()
                        {
                            new TTablePropertyInfo(typeof(TTestTableEntityDot).GetProperty("RowKey"), "RowKey"),
                            new TTablePropertyInfo(typeof(TTestTableEntityDot).GetProperty("PartitionKey"), "PartitionKey"),
                            new TTablePropertyInfo(typeof(TTestTableEntityDot).GetProperty("Timestamp"), "Timestamp")
                        }
                    }
                },
                typeof(TTestTableEntityDot).Name,
                RetryPolicies.Retry(3, new TimeSpan(0)),
                null);
        }

        /// <summary>
        ///A test for TTableStorageEngine Constructor
        ///</summary>
        [TestMethod()]
        public void TTableStorageEngineConstructorTest()
        {
            Uri serviceRoot = CloudStorageAccount.DevelopmentStorageAccount.TableEndpoint;
            StorageCredentials credentials = Credentials;
            string tableName = typeof(TTestTableEntityDot).Name;
            RetryPolicy policy = RetryPolicies.Retry(3, new TimeSpan(0, 0, 30));

            TTablePropertyInfo tablePropInfo = new TTablePropertyInfo(typeof(TTestTableEntityDot).GetType().GetProperties().Where(pi => pi.PropertyType == typeof(int)).FirstOrDefault(), "testfield");

            TTableStorageInfo tableInfo = new TTableStorageInfo();
            tableInfo.ClassType = this.GetType();
            tableInfo.TableName = this.GetType().Name;
            tableInfo.PropertyList = new List<TTablePropertyInfo>() { tablePropInfo };

            List<TTableStorageInfo> tableInfoList = new List<TTableStorageInfo>() { tableInfo };
            TTableStorageEngine target = new TTableStorageEngine(serviceRoot, credentials, tableInfoList, tableName, policy, null);
            Assert.IsNotNull(target);
        }

        /// <summary>
        ///A test for CreateDataServiceContent
        ///</summary>
        [TestMethod()]
        [DeploymentItem("BetterAzureTableStorage.dll")]
        public void CreateDataServiceContentTest()
        {
            Guid newGuid = Guid.NewGuid();
            DateTime newDateTime = DateTime.Now;
            string dateTimeString = newDateTime.ToString(TableStorageConstants.DateTimeFormat);
            TTestTableEntityDot entity = new TTestTableEntityDot(newGuid.ToString(), newGuid.ToString(), newDateTime);

            PrivateObject param0 = new PrivateObject(GetTestTableEntityDotEngine());
            TTableStorageEngine_Accessor target = new TTableStorageEngine_Accessor(param0);

            XElement expected = new XElement(TTableStorageEngine_Accessor.atomNS + "content",
                new XAttribute("type", "application/xml"),
                new XElement(TTableStorageEngine_Accessor.metaNS + "properties",
                    new XElement(TTableStorageEngine_Accessor.dataNS + "RowKey",
                        new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.String"), newGuid.ToString()),
                    new XElement(TTableStorageEngine_Accessor.dataNS + "PartitionKey",
                        new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.String"), newGuid.ToString()),
                    new XElement(TTableStorageEngine_Accessor.dataNS + "Timestamp",
                        new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.DateTime"), dateTimeString)));

            XElement actual;
            actual = target.CreateDataServiceContent(entity);
            Assert.IsTrue(XNode.DeepEquals(expected, actual));
        }

        /// <summary>
        ///A test for CreateDataServiceEntry
        ///</summary>
        [TestMethod()]
        [DeploymentItem("BetterAzureTableStorage.dll")]
        public void CreateDataServiceEntryTest()
        {
            Guid newGuid = Guid.NewGuid();
            DateTime newDateTime = DateTime.Now;
            string dateTimeString = newDateTime.ToString(TableStorageConstants.DateTimeFormat);
            TTestTableEntityDot entity = new TTestTableEntityDot(newGuid.ToString(), newGuid.ToString(), newDateTime);

            PrivateObject param0 = new PrivateObject(this.GetTestTableEntityDotEngine());
            TTableStorageEngine_Accessor target = new TTableStorageEngine_Accessor(param0);


            XElement expected =
                new XElement(TTableStorageEngine_Accessor.atomNS + "entry",
                    new XAttribute(XNamespace.Xmlns + "d", TTableStorageEngine_Accessor.dataNS),
                    new XAttribute(XNamespace.Xmlns + "m", TTableStorageEngine_Accessor.metaNS),
                new XElement(TTableStorageEngine_Accessor.atomNS + "title"),
                new XElement(TTableStorageEngine_Accessor.atomNS + "updated", newDateTime.ToUniversalTime().ToString(TableStorageConstants.DateTimeFormat)),
                new XElement(TTableStorageEngine_Accessor.atomNS + "author",
                    new XElement(TTableStorageEngine_Accessor.atomNS + "name")),
                new XElement(TTableStorageEngine_Accessor.atomNS + "id"),
                new XElement(TTableStorageEngine_Accessor.atomNS + "content",
                new XAttribute("type", "application/xml"),
                new XElement(TTableStorageEngine_Accessor.metaNS + "properties",
                    new XElement(TTableStorageEngine_Accessor.dataNS + "RowKey",
                        new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.String"), newGuid.ToString()),
                    new XElement(TTableStorageEngine_Accessor.dataNS + "PartitionKey",
                        new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.String"), newGuid.ToString()),
                    new XElement(TTableStorageEngine_Accessor.dataNS + "Timestamp",
                        new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.DateTime"), dateTimeString))));

            XElement actual;
            actual = target.CreateDataServiceEntry(entity);
            XElement expectedUpdatedElement = expected.Element(TTableStorageEngine_Accessor.atomNS + "updated");
            XElement actualUpdatedElement = actual.Element(TTableStorageEngine_Accessor.atomNS + "updated");
            Assert.IsNotNull(expectedUpdatedElement);
            Assert.IsNotNull(actualUpdatedElement);
            Assert.IsTrue(DateTime.Parse(actualUpdatedElement.Value) - DateTime.Parse(expectedUpdatedElement.Value) < TimeSpan.FromSeconds(3));

            expectedUpdatedElement.Remove();
            actualUpdatedElement.Remove();

            Assert.AreEqual(expected.Value, actual.Value);
        }

        /// <summary>
        ///A test for GetParseMethod
        ///</summary>
        [TestMethod()]
        [DeploymentItem("BetterAzureTableStorage.dll")]
        public void GetParseMethodTest()
        {
            TTableStorageEngine engine = this.GetTestTableEntityDotEngine();

            PrivateObject param0 = new PrivateObject(engine);

            TTableStorageEngine_Accessor target = new TTableStorageEngine_Accessor(param0);
            Type type = typeof(DateTime);
            MethodInfo expected = typeof(DateTime).GetMethods().FirstOrDefault(m => m.Name == "Parse" && m.GetParameters().Count() == 1 && m.GetParameters()[0].ParameterType == typeof(string));
            MethodInfo actual;
            actual = target.GetParseMethod(type);
            Assert.AreEqual(expected, actual);

            type = typeof(TTestTableEntityDot);
            expected = null;
            actual = null;
            actual = target.GetParseMethod(type);
            Assert.AreEqual(expected, actual);
        }

        /// <summary>
        ///A test for Materialize
        ///</summary>
        [TestMethod()]
        [DeploymentItem("BetterAzureTableStorage.dll")]
        public void MaterializeTest()
        {
            Guid testGuid = Guid.NewGuid();
            DateTime testDateTime = DateTime.UtcNow;
            string testDateTimeString = testDateTime.ToUniversalTime().ToString(TableStorageConstants.DateTimeFormat);

            TTableStorageEngine engine = this.GetTestTableEntityDotEngine();

            PrivateObject param0 = new PrivateObject(engine);
            TTableStorageEngine_Accessor target = new TTableStorageEngine_Accessor(param0);
            TTestTableEntityDot actual = new TTestTableEntityDot();
            TTestTableEntityDot expected = new TTestTableEntityDot(testGuid.ToString(), testGuid.ToString(), testDateTime);

            XElement entryElement =
                new XElement(TTableStorageEngine_Accessor.atomNS + "entry",
                    new XAttribute(XNamespace.Xmlns + "d", TTableStorageEngine_Accessor.dataNS),
                    new XAttribute(XNamespace.Xmlns + "m", TTableStorageEngine_Accessor.metaNS),
                    new XElement(TTableStorageEngine_Accessor.atomNS + "title"),
                    new XElement(TTableStorageEngine_Accessor.atomNS + "updated", testDateTimeString),
                    new XElement(TTableStorageEngine_Accessor.atomNS + "author",
                        new XElement(TTableStorageEngine_Accessor.atomNS + "name")),
                        new XElement(TTableStorageEngine_Accessor.atomNS + "id"),
                        new XElement(TTableStorageEngine_Accessor.atomNS + "content",
                            new XAttribute("type", "application/xml"),
                            new XElement(TTableStorageEngine_Accessor.metaNS + "properties",
                                new XElement(TTableStorageEngine_Accessor.dataNS + "RowKey",
                                    new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.String"), testGuid.ToString()),
                                new XElement(TTableStorageEngine_Accessor.dataNS + "PartitionKey",
                                    new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.String"), testGuid.ToString()),
                                new XElement(TTableStorageEngine_Accessor.dataNS + "Timestamp",
                                    new XAttribute(TTableStorageEngine_Accessor.metaNS + "type", "Edm.DateTime"), testDateTimeString))));

            target.Materialize(actual, entryElement);
            Assert.AreEqual(expected.PartitionKey, actual.PartitionKey);
            Assert.AreEqual(expected.RowKey, actual.RowKey);
            Assert.AreEqual(expected.Timestamp.ToString(TableStorageConstants.DateTimeFormat), actual.Timestamp.ToString(TableStorageConstants.DateTimeFormat));
        }

        [TestMethod]
        [ExpectedException(typeof(TTableStorageException))]
        public void FailMaterializeParseMethod()
        {
            TTableStorageEngine engine = this.GetTestTableEntityDotEngine();

            TTableStorageEngine_Accessor target = new TTableStorageEngine_Accessor(new PrivateObject(engine));
            TTestEnum entity = TTestEnum.Value1;
            target.Materialize(entity, new XElement(TTableStorageEngine_Accessor.atomNS + "entry"));
        }

        /// <summary>
        ///A test for ParseMethods
        ///</summary>
        [TestMethod()]
        [DeploymentItem("BetterAzureTableStorage.dll")]
        public void ParseMethodsTest()
        {
            Dictionary<Type, MethodInfo> expected = new Dictionary<Type, MethodInfo>();
            Dictionary<Type, MethodInfo> actual;
            TTableStorageEngine_Accessor.ParseMethods = expected;
            actual = TTableStorageEngine_Accessor.ParseMethods;
            Assert.AreEqual(expected, actual);
        }

        /// <summary>
        /// Tests the TTableStorageEngine.CreateEntityBatchRequest method.
        /// </summary>
        /// <remarks>
        /// This test will specifically test the error conditions that will throw exceptions in the method.</remarks>
        [TestMethod]
        public void CreateEntityBatchRequestTest()
        {
            TTableStorageEngine engine = this.GetTestTableEntityDotEngine();
            TTableStorageEngine_Accessor accessor = new TTableStorageEngine_Accessor(new PrivateObject(engine));

            List<TTableEntityTransactionInfo> targets = null;
            bool hasException = false;

            try
            {
                engine.CreateEntityBatchRequest(targets);
            }
            catch (Exception e)
            {
                Assert.AreEqual<Type>(typeof(ArgumentNullException), e.GetType());
                Assert.AreEqual<string>("targets", (e as ArgumentNullException).ParamName);
                hasException = true;
            }

            Assert.IsTrue(hasException);
            hasException = false;

            targets = new List<TTableEntityTransactionInfo>();

            try
            {
                engine.CreateEntityBatchRequest(targets);
            }
            catch (Exception e)
            {
                Assert.AreEqual<Type>(typeof(ArgumentException), e.GetType());
                Assert.AreEqual<string>("Targets must contain at least one element to generate a request.", (e as ArgumentException).Message);
                hasException = true;
            }

            Assert.IsTrue(hasException);
            hasException = false;

            targets.Add(new TTableEntityTransactionInfo(new TTestTableEntityDot(), EntityStates.Added));
            targets[0].Verb = HttpConstants.Get;

            try
            {
                engine.CreateEntityBatchRequest(targets);
            }
            catch (Exception e)
            {
                Assert.AreEqual<Type>(typeof(TTableStorageException), e.GetType());
                Assert.AreEqual<string>("Unsupported method type 'GET'", (e as TTableStorageException).Message);
                hasException = true;
            }

            Assert.IsTrue(hasException);
        }
    }
}
